<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Page Title</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" type="text/css" media="screen" href="main.css">
</head>
<body>
    <div>
      {{name}}
    </div>
  <script>
    // 递归遍历，使传递进来的对象响应化
    function observe(obj) {
      // 判断类型
      if (!obj || typeof obj !== 'object') {
        return
      }
      Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key])
      })
    }

    function defineReactive(obj, key, val) {
      // 递归子属性
      observe(val)
      let dp = new Dep()
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
          console.log('get value')
          // 将 Watcher 添加到订阅
          if (Dep.target) {
            dp.addSub(Dep.target)
          }
          return val
        },
        set: function reactiveSetter(newVal) {
          console.log('change value')
          val = newVal
          // 执行 watcher 的 update 方法
          dp.notify()
        }
      })
    }

    // 通过 Dep类 解耦属性的依赖收集和更新操作（Dep 类是一个简单的观察者模式的实现）
    class Dep {
      constructor() {
        this.subs = []
      }
      // 添加依赖
      addSub(sub) {
        this.subs.push(sub)
      }
      // 更新
      notify() {
        this.subs.forEach(sub => {
          sub.update()
        })
      }
    }

    // 全局属性，通过该属性配置 Watcher
    Dep.target = null

    class Watcher {
      constructor(obj, key, cb) {
        // 将 Dep.target 指向自己
        // 然后触发属性的 getter 添加监听
        // 最后将 Dep.target 置空
        Dep.target = this
        this.cb = cb
        this.obj = obj
        this.key = key
        this.value = obj[key]
        Dep.target = null
      }
      update() {
        // 获得新值
        this.value = this.obj[this.key]
        // 调用 update 方法更新 DOM
        this.cb(this.value)
      }
    }

    // 组件初始化的时候执行 observe 方法
    var data = { name: 'yck' }

    observe(data)

    // 编译器工作：解析模板，收集依赖，创建 Watcher 和 update 函数（这里的 update 函数会在 Watcher 执行自身的 update 函数时被调用，从而更新 DOM）
    // 完整的编译器再执行 更新DOM 操作之前，会有一个 patch 过程，该过程会执行 diff， 进行虚拟DOM的比对，然后再更新 DOM
    function update(value) {
      document.querySelector('div').innerText = value
    }

    // 模拟解析到 `{{name}}` 触发的操作
    new Watcher(data, 'name', update)

    // update Dom innerText
    data.name = 'yyy'
  </script>
</body>
</html>
